﻿using AutoMapper;
using MediatR;
using Microsoft.EntityFrameworkCore;
using WeatherHistory.Data.Entities;
using WeatherHistory.Data.Repositories.Interfaces;
using WeatherHistory.Shared.Exceptions;

namespace WeatherHistory.Api.CommandsQueries.Commands.Weathers.Update;

public class UpdateWeathersCommandHandler : IRequestHandler<UpdateWeathersCommandRequest, UpdateWeathersCommandResponse>
{
    private readonly IMapper _mapper;
    private readonly IRegionRepository _regionRepository;
    private readonly IWeatherRepository _weatherRepository;
    private readonly IWeatherRegionRelationRepository _weatherRegionRelationRepository;

    public UpdateWeathersCommandHandler(IMapper mapper, IRegionRepository regionRepository, IWeatherRepository weatherRepository, IWeatherRegionRelationRepository weatherRegionRelationRepository)
    {
        _mapper = mapper;
        _regionRepository = regionRepository;
        _weatherRepository = weatherRepository;
        _weatherRegionRelationRepository = weatherRegionRelationRepository;
    }

    public async Task<UpdateWeathersCommandResponse> Handle(UpdateWeathersCommandRequest request, CancellationToken cancellationToken)
    {
        var oldRegion = await _regionRepository
            .AsQueryable()
            .Include(x => x.WeatherRegionRelations)
            .FirstOrDefaultAsync(x => x.Id == request.OldRegionId, cancellationToken);
        if (oldRegion == null) throw new NotFoundException($"Регион с id '{request.OldRegionId}' не найден");
        if (oldRegion.WeatherRegionRelations.Count == 0) throw new NotFoundException($"Для региона '{request.OldRegionId}' нет записей погоды");
        
        var lastCreatedWeatherId = oldRegion.WeatherRegionRelations.MaxBy(x => x.CreatedAt)!.WeatherId;

        var targetRegion = await _regionRepository
            .AsQueryable()
            .Include(x => x.Forecasts)
            .Include(x => x.WeatherRegionRelations)
            .FirstOrDefaultAsync(x => x.Id == request.RegionId, cancellationToken);
        if (targetRegion == null) throw new NotFoundException($"Регион с id '{request.RegionId}' не найден");
        
        var weather = await _weatherRepository
            .AsQueryable()
            .OrderByDescending(x => x.CreatedAt)
            .Include(x => x.Regions)
            .Include(x => x.Forecasts)
            .Include(x => x.WeatherRegionRelations)
            .FirstAsync(x => x.Id == lastCreatedWeatherId, cancellationToken);
     
        targetRegion.Name = request.RegionName;

        weather.Temperature = request.Temperature;
        weather.Humidity = request.Humidity;
        weather.PrecipitationAmount = request.PrecipitationAmount;
        weather.WindSpeed = request.WindSpeed;
        weather.WeatherCondition = request.WeatherCondition;
        weather.MeasurementDateTime = request.MeasurementDateTime.ToUniversalTime();
        weather.Forecasts = new List<Forecast>();
        
        if (request.WeatherForecast != null && request.WeatherForecast.Count != 0)
        {
            var forecastsToAdd = targetRegion.Forecasts.Where(x => request.WeatherForecast.Contains(x.Id)).ToList();
            var invalidForecastsIds = request.WeatherForecast.Except(forecastsToAdd.Select(x => x.Id)).ToList();
            if (invalidForecastsIds.Count != 0) throw new NotFoundException($"Прогнозы погоды '{string.Join("', '", invalidForecastsIds)}' для региона '{request.RegionId}' не найдены");
            weather.Forecasts = forecastsToAdd;
        }
        
        var storedWeatherRelation =  await _weatherRegionRelationRepository
            .AsQueryable()
            .Include(x => x.Region)
            .Include(x => x.Weather).ThenInclude(x => x.Forecasts)
            .FirstOrDefaultAsync(x => x.RegionId == targetRegion.Id && x.WeatherId == weather.Id, cancellationToken);
        if (storedWeatherRelation == null)
        {
            storedWeatherRelation = new WeatherRegionRelation();
            storedWeatherRelation.Region = targetRegion;
            storedWeatherRelation.Weather = weather;
            await _weatherRegionRelationRepository.InsertAsync(storedWeatherRelation, cancellationToken);
        }
        if (targetRegion.Id != oldRegion.Id)
        {
            var weatherRelation = oldRegion.WeatherRegionRelations.First(x => x.WeatherId == weather.Id);
            await _weatherRegionRelationRepository.DeleteAsync(weatherRelation, cancellationToken);
        }

        await _regionRepository.UpdateAsync(targetRegion, cancellationToken);
        await _weatherRepository.UpdateAsync(weather, cancellationToken);
        
        return _mapper.Map<UpdateWeathersCommandResponse>(storedWeatherRelation);
    }
}